import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class DrawObject
{
	// DEFAULT DrawObject abilities:
	public boolean IsLearningNeuron()
	{
		return IsInLearningNeuronState;
	}

	public boolean IsTextField()
	{
		return false;
	}

	public boolean IsLine()
	{
		return false;
	}

	public boolean IsNeuron()
	{
		return false;
	}

	public boolean IsNeuronInputField()
	{
		return false;
	}

	public boolean IsSampleAndHold()
	{
		return false;
	}

	public boolean IsVisualField()
	{
		return false;
	}

	public boolean IsVisualFieldPixel()
	{
		return false;
	}

	public boolean CanBeDeletedByUser()
	{
		return false;
	}

	public boolean CanBeExcitedByUser()
	{
		return false;
	}

	public boolean CanBeMovedByUser()
	{
		return false;
	}

	public boolean CanBeSelectedByUser()
	{
		return false;
	}
	// end of DEFAULT DrawObject abilities

	private static final short FileFormatVersion = 3;

	public static final long ID_UNSET = -1; // ATTENTION: code assumes it's < 0; use when any kind of id has not been assigned
	
	public static final String TextFieldDefaultText = "?";

	private static final Font DrawStringDefaultFont = new Font("Courier New", Font.PLAIN, 20);
	private static final Font DrawStringSmallFont = new Font("Courier New", Font.PLAIN, 14);

	private static long CompoundIdNext = 0;	// Convention: we ALWAYS use long values for Ids, we do hardly need more space but can avoid (Integer) overflows!

	private static boolean ReducedDrawOrderEnabled = false;

	public static final long PROPERTY_DONT_DRAW_IN_REDUCED_DRAWING_MODE = (1L << 0);

	protected long Properties = 0;
	protected Point Pos = new Point(0, 0);
	protected Point Size = new Point(0, 0);
	protected boolean IsHighlighted = false;
	protected boolean IsZombie = false;
	protected boolean IsPartOfMultiSelection = false;
	protected boolean IsCommentField = false;

	// properties that apply to certain class types only, we assign them
	// to all classes to be able to easily save/load state with one
	// method (some memory wasted, but negligible - some few class types
	// must still override save/load state methods):

	// e.g. Neuron/NeuronInputNode/NeuronInputField/NeuronOutputNode:
	protected long CompoundId = ID_UNSET; // could be the same among SEVERAL files (but unique in one and the same file), pay attention when loading several files!

	// TextField specific:
	protected String Text = TextFieldDefaultText;

	// Neuron specific:
	protected int StrengthMax = 100; // means percent, don't use floating point values to avoid rounding errors (see ExcitementTriggerValue)
	protected int Strength = 0;
	protected int StrengthTrigger = 0;
	protected int StrengthIncreaseStep = 5; // add 5 percent, not too much for faster increasing, not too few to avoid we can't demand e.g. exactly 3 fields to be activated at once, because of the division reminder
	protected boolean IsNegative = false;
	protected boolean IsInLearningNeuronState = false;

	protected final static ArrayList<String> EmptyHormoneNames = new ArrayList<String>(); // save memory by always referring to the same empty collection
	protected final static ArrayList<Double> EmptyHormoneStrengths = new ArrayList<Double>(); // save memory by always referring to the same empty collection

	protected ArrayList<String> HormoneNames = EmptyHormoneNames; // save memory by always referring to the same empty collection
	protected ArrayList<Double> HormoneStrengths = EmptyHormoneStrengths; // save memory by always referring to the same empty collection

	// VisualFieldPixel specific:
	protected boolean IsEnabled;

	// states not saved to file:
	protected double ExcitementCurrent;
	protected double ExcitementAddForNextTick; // temporary
	protected double ExcitementLossPerTick;
	protected double ExcitementTriggerValue = 0.99; // use "easy" value to easily understand how to set Strengths; don't use exactly 1.0, there seem to be rounding errors in floating point calculation (tested)

	protected DrawObject ExcitementPartner = null;
	protected boolean ExcitementPartnerServedInThisTick = false;
	protected double ExcitementPartnerExcitement;

	protected final int ExcitementOnceCorrectionFactor = 2; // HACK!!! Without, activation is not visible on GUI (probably because redrawing runs in own Java thread) - if required, please search code for further usages of ExcitementOnceCorrectionFactor (e.g. in Neuron)
	protected final int ExcitementOnceMilliseconds = (1000 / Simulation.GetTicksPerSecond()) * ExcitementOnceCorrectionFactor; // more than one tick necessary, probably because of delayed GUI redrawing
	protected final int ExcitementNeuronInputNodeMilliseconds = 1000;
	protected final int ExcitementVisualFieldPixelMilliseconds = 2000;

	protected int ExcitementHistoryLength = 1; // hack for drawing excitement bar correctly, the problem is that Java's repaint() runs in an own thread (https://en.wikipedia.org/wiki/Event_dispatching_thread) and redrawing seems to have been done when some excitement has already been lost
	protected double ExcitementHistory[] = new double[ExcitementHistoryLength]; // determine max excitement during the last ExcitementHistoryLength ticks
	protected int ExcitementHistoryWriteIndex = 0;

	// parent stuff
	protected DrawObject ParentDrawObject = null;
	// ^
	// NOTE: ChildDrawObjects are DrawObjects tied to a ParentDrawObject.
	// When any ChildDrawObject is to be moved, it doesn't apply instantly but
	// tells his ParentDrawObject to move. The parent moves itself and calls
	// ChildDrawObject.MoveThis(), which does not move its parent any more.
	// Similar applies (sometimes) for highlighting and other DrawObject actions.
	// This is required as the user could e.g. select multiple, but not all,
	// VisualFieldPixels, but ALL pixels AND the VisualField itself should to be moved.
	// A problem is that moving MULTIPLE VisualFieldPixels will lead to calling
	// VisualField.Move() SEVERAL times, so that the whole VisualField with its
	// pixels could "fly away" as being moved multiple times by the same MoveAmount.

	public static Point AlignOnGrid(Point P)
	{
		Point POnGrid = new Point(P);

		// NOTE: don't touch this... just % did not work, problems if
		// value negative (tested)

		int GridSizex = 50;
		int GridSizey = 50;

		if (POnGrid.x < 0)
		{
			POnGrid.x -= GridSizex / 2;
			POnGrid.x = -POnGrid.x;
			POnGrid.x = POnGrid.x - (int) (POnGrid.x % 50);
			POnGrid.x = -POnGrid.x;
		}
		else
		{
			POnGrid.x += GridSizex / 2;
			POnGrid.x = POnGrid.x - (int) (POnGrid.x % 50);
		}

		if (POnGrid.y < 0)
		{
			POnGrid.y -= GridSizey / 2;
			POnGrid.y = -POnGrid.y;
			POnGrid.y = POnGrid.y - (int) (POnGrid.y % 50);
			POnGrid.y = -POnGrid.y;
		}
		else
		{
			POnGrid.y += GridSizey / 2;
			POnGrid.y = POnGrid.y - (int) (POnGrid.y % 50);
		}

		return POnGrid;
	}

	public static long GetNewCompoundId()
	{
		long CompoundIdNew = CompoundIdNext;
		CompoundIdNext++;
		return CompoundIdNew;
	};

	public static void SetMinimalCompoundId(long CompoundIdNextNew) // use when loading a SubSystem, just pass any minimal number that cannot be in use as CompoundId yet
	{
		if (CompoundIdNextNew >= 0)
			CompoundIdNext = CompoundIdNextNew; // CompoundIdNext is static and thus is unique as long as the program is executed
		else
			System.out.println("error in DrawObject.SetMinimalCompoundId(): passed value not useful!");
	};

	public static void SetReducedDrawOrderEnabled(boolean ReducedDrawOrderEnabledNew)
	{
		ReducedDrawOrderEnabled = ReducedDrawOrderEnabledNew;
	};

	static Point UnZoomPoint(Point p)
	{
		return new Point(UnZoomPointX(p.x), UnZoomPointY(p.y));
	};

	static int UnZoomPointX(int x)
	{
		return (int) ((x - ZoomManager.GetZoomShiftX()) / ZoomManager.GetZoomFactor());
	};

	static int UnZoomPointY(int y)
	{
		return (int) ((y - ZoomManager.GetZoomShiftY()) / ZoomManager.GetZoomFactor());
	};

	static Point ZoomAmount(Point p)
	{
		return new Point(ZoomAmountX(p.x), ZoomAmountX(p.y));
	};

	static int ZoomAmountX(int x)
	{
		return (int) (x / ZoomManager.GetZoomFactor());
	};

	static int ZoomAmountY(int y)
	{
		return (int) (y / ZoomManager.GetZoomFactor());
	}

	static Point ZoomPoint(Point p)
	{
		return new Point(ZoomPointX(p.x), ZoomPointY(p.y));
	}

	static int ZoomPointX(int x)
	{
		return (int) (x * ZoomManager.GetZoomFactor() + ZoomManager.GetZoomShiftX());
	}

	static int ZoomPointY(int y)
	{
		return (int) (y * ZoomManager.GetZoomFactor() + ZoomManager.GetZoomShiftY());
	}

	protected void AddExcitementForCurrentTick(double ExcitementAdd)
	{
		// limit new excitement value to avoid that multiple adds lead to such a high value that loss has no effect any more
		ExcitementCurrent = LimitExcitement(ExcitementCurrent + ExcitementAdd);
	}

	protected void AddExcitementForNextTick(double ExcitementAdd)
	{
		// limit new excitement value to avoid that multiple adds lead to such a high value that loss has no effect any more
		ExcitementAddForNextTick = LimitExcitement(ExcitementAddForNextTick + ExcitementAdd);
	}

	public void AddProperty(long PropertyBitMask)
	{
		Properties |= PropertyBitMask;
	}

	protected void AfterCollectingAllExcitementForNextTick()
	{
		// you may @Override this
	}

	protected boolean ContainsPoint(Point PointPos)
	{
		if (PointPos.x >= Pos.x && PointPos.x < Pos.x + Size.x)
			if (PointPos.y >= Pos.y && PointPos.y < Pos.y + Size.y)
				return true;

		return false;
	}

	protected boolean DoesActivate(double ExcitementToTest)
	{
		return (ExcitementToTest >= ExcitementTriggerValue); // user greater EQUAL!
	}

	protected void Draw(Graphics g)
	{
		// you NEED to @Override this!
	}

	protected void DrawLine(Graphics g, int x1, int y1, int x2, int y2)
	{
		if (ZoomManager.GetZoomFactor() != 1.0)
		{
			x1 = ZoomPointX(x1);
			y1 = ZoomPointY(y1);
			x2 = ZoomPointX(x2);
			y2 = ZoomPointY(y2);
		}
		g.drawLine(x1, y1, x2, y2);
	}

	protected void DrawRect(Graphics g, int x, int y, int width, int height)
	{
		if (ZoomManager.GetZoomFactor() != 1.0)
		{
			x = ZoomPointX(x);
			y = ZoomPointY(y);
			width = (int) (width * ZoomManager.GetZoomFactor());
			height = (int) (height * ZoomManager.GetZoomFactor());
		}
		g.drawRect(x, y, width, height);
	}

	protected void DrawString(Graphics g, String Text, int x, int y)
	{
		if (ZoomManager.GetZoomFactor() >= 1.0) // don't draw strings if shrunk, not readable anyway
		{
			x = ZoomPointX(x);
			y = ZoomPointY(y);

			g.drawString(Text, x, y);
		}
	}

	protected void DrawThickLine(Graphics g, int x1, int y1, int x2, int y2/*, int thickness */)
	{ // https://www.rgagnon.com/javadetails/java-0260.html, thanks... update: nope, think again

		// This code does not work, the line only has a thickness if drawn
		// absolutely vertically or horizontally...
		/*
		// The thick line is in fact a filled polygon
		int dX = x2 - x1;
		int dY = y2 - y1;
		
		double lineLength = Math.sqrt(dX * dX + dY * dY);
		
		double scale = (double) (thickness) / (2 * lineLength);
		
		// The x,y increments from an end point needed to create a rectangle...
		double ddx = -scale * (double) dY;
		double ddy = +scale * (double) dX;
		ddx += (ddx > 0) ? 0.5 : -0.5;
		ddy += (ddy > 0) ? 0.5 : -0.5;
		int dx = (int)ddx;
		int dy = (int)ddy;
		
		// Now we can compute the corner points...
		int xPoints[] = new int[4];
		int yPoints[] = new int[4];
		
		xPoints[0] = x1 + dx; yPoints[0] = y1 + dy;
		xPoints[1] = x1 - dx; yPoints[1] = y1 - dy;
		xPoints[2] = x2 - dx; yPoints[2] = y2 - dy;
		xPoints[3] = x2 + dx; yPoints[3] = y2 + dy;
		
		g.fillPolygon(xPoints, yPoints, 4);
		*/

		// own code: place line ends "in circle" around start/end point,
		// so that there is always a set of lines pointing in the same
		// direction and having the same start/end point distances
		// from each other:

		DrawLine(g, x1, y1, x2, y2);

		int OffsetxLast = Integer.MAX_VALUE;
		int OffsetyLast = Integer.MAX_VALUE;

		for (int r = 0; r < 360; r += 10)
		{
			int Offsetx = (int) Math.sin(Math.toRadians((double) r)) * 1;
			int Offsety = (int) Math.cos(Math.toRadians((double) r)) * 1;

			if (Offsetx != OffsetxLast || Offsety != OffsetyLast)
			{
				DrawLine(g, x1 + Offsetx, y1 + Offsety, x2 + Offsetx, y2 + Offsety);
			}

			OffsetxLast = Offsetx;
			OffsetyLast = Offsety;
		}
	}

	protected void DrawThickRect(Graphics g, int x, int y, int width, int height)
	{
		// DrawThickLine() does the zooming!

		DrawThickLine(g, x, y, x + width, y);
		DrawThickLine(g, x + width, y, x + width, y + height);
		DrawThickLine(g, x + width, y + height, x, y + height);
		DrawThickLine(g, x, y + height, x, y);
	}

	protected void FillRect(Graphics g, int x, int y, int width, int height)
	{
		if (ZoomManager.GetZoomFactor() != 1.0)
		{
			x = ZoomPointX(x);
			y = ZoomPointY(y);
			width = (int) (width * ZoomManager.GetZoomFactor());
			height = (int) (height * ZoomManager.GetZoomFactor());
		}
		g.fillRect(x, y, width, height);
	}

	protected ArrayList<DrawObject> GetChildDrawObjects()
	{
		return new ArrayList<DrawObject>(); // override this if necessary!
	}

	protected long GetCompoundId()
	{
		return CompoundId;
	}

	protected Font GetDefaultFont()
	{
		return DrawStringDefaultFont;
	}

	protected Color GetExcitementColor()
	{
		// fade from yellow to black

		return GetExcitementColor(Color.BLACK);
	}

	protected Color GetExcitementColor(Color BaseColor)
	{
		// fade from yellow to passed base color

		float ExcitementFloat = (float) Math.max(0.0, Math.min(1.0, GetExcitementMaxFromExcitementHistory())); // do NOT use LimitExcitement(), because this is for determining color only!

		int r = (int) (ExcitementFloat * 255.0f);
		int g = (int) (ExcitementFloat * 255.0f);

		if (ExcitementFloat > 0.1f)
			return new Color(r / 255.0f, g / 255.0f, 0.0f);
		else
			return BaseColor;
	}

	protected double GetExcitementMaxFromExcitementHistory()
	{
		double ExcitementMax = 0.0;

		for (int m = 0; m < ExcitementHistoryLength; m++)
			if (ExcitementHistory[m] > ExcitementMax)
				ExcitementMax = ExcitementHistory[m];

		return ExcitementMax;
	}

	public DrawObject GetParentDrawObject()
	{
		return ParentDrawObject;
	}

	public long GetProperties()
	{
		return Properties;
	}

	protected Font GetSmallFont()
	{
		return DrawStringSmallFont;
	}

	public int GetStrengthMax()
	{
		return StrengthMax;
	}

	public int GetStrengthTrigger()
	{
		return StrengthTrigger;
	}

	public boolean HasParent()
	{
		return (ParentDrawObject != null);
	}

	protected void IncreaseStrength()
	{
		Strength += StrengthIncreaseStep;
		if (Strength > StrengthMax)
			Strength = StrengthMax;
	}

	protected void IncreaseStrengthTrigger()
	{
		StrengthTrigger += 5;
		if (StrengthTrigger > StrengthMax)
			StrengthTrigger = StrengthMax;
	}

	protected void InitializeExcitement(int MillisecondsUntilFullLoss)
	{
		ExcitementCurrent = 0.0; // preset

		int TickLengthInMilliseconds = (1000 / Simulation.GetTicksPerSecond());

		ExcitementLossPerTick = 1.0 * ((double) TickLengthInMilliseconds / (double) MillisecondsUntilFullLoss);
	}

	protected boolean IsActivated()
	{
		return DoesActivate(ExcitementCurrent);
	}

	protected boolean IsPartOfMultiSelection()
	{
		return IsPartOfMultiSelection;
	}

	public boolean IsReducedDrawOrderEnabled()
	{
		return ReducedDrawOrderEnabled;
	}

	protected boolean IsThereNoteworthyExcitement()
	{
		return (ExcitementCurrent > 0.1);
	}

	@SuppressWarnings("static-access")
	protected boolean IsWithinWindowBounds(DrawObject DrawObject, int RecursionCounter)
	{
		if (RecursionCounter >= 10) // just to make sure... :-P
		{
			System.out.println("internal error: IsWithinWindowBounds() stuck in recursion loop (probably)!");
			return false;
		}

		if (DrawObject == null) // should not happen
			return false;

		// NOTE: call UpdateMainWindowSizeInfo() whenever necessary to make out-of-window-bounds check work!
		// Manipulate UpdateMainWindowSizeInfo() to test out-of-window-bounds check (verified on 2022-07-24_15-59).

		if (DrawObject.IsCommentField)
		{
			// ALWAYS draw parts of any CommentField, to avoid that its "frame Lines"
			// are hidden because their nodes are out of the window area (see comment below);
			// We can assume there is a limited number of CommentFields, not thousands, like
			// Neurons or VisualFieldPixels, so that should not have a notable impact on
			// drawing speed.

			return true;
		}

		if (DrawObject.IsLine())
		{
			// do not check DrawObject.Pos and .Size, it's not useful for Line!
			if (IsWithinWindowBounds(((Line) DrawObject).GetStartDrawObject(), RecursionCounter + 1) || IsWithinWindowBounds(((Line) DrawObject).GetEndDrawObject(), RecursionCounter + 1))
			{
				return true;
			}
			else
			{
				// NOTE: if the user draws Lines which go over large distances
				// in a "gross" way, some Lines might not be drawn although they
				// should be visible. We currently accept this fault, maybe
				// fix this later... (or tell the user not to build nonsense).

				/*
				// Lines (probably) ACROSS visible screen area?
				// DOES ALSO NOT WORK COMPLETELY as further attached, (short)
				// Lines may get hidden so that the screen-crossing Lines
				// are not drawn, too!
				if (((Line) DrawObject).GetStartDrawObject().Pos.x < 0 &&
						((Line) DrawObject).GetEndDrawObject().Pos.x > 0)
				{
					if (((Line) DrawObject).GetStartDrawObject().Pos.y < 0 &&
							((Line) DrawObject).GetEndDrawObject().Pos.y > 0)
						return true;
					if (((Line) DrawObject).GetEndDrawObject().Pos.y < 0 &&
							((Line) DrawObject).GetStartDrawObject().Pos.y > 0)
						return true;
				}
				if (((Line) DrawObject).GetEndDrawObject().Pos.x < 0 &&
						((Line) DrawObject).GetStartDrawObject().Pos.x > 0)
				{
					if (((Line) DrawObject).GetStartDrawObject().Pos.y < 0 &&
							((Line) DrawObject).GetEndDrawObject().Pos.y > 0)
						return true;
					if (((Line) DrawObject).GetEndDrawObject().Pos.y < 0 &&
							((Line) DrawObject).GetStartDrawObject().Pos.y > 0)
						return true;
				}
				*/
			}
		}
		else
		{
			// ATTENTION: *DrawObject*.Pos/Size!
			// ATTENTION: these are the ORIGINAL positions and sizes stored in memory,
			// THEY ARE NOT AFFECTED BY THE ZOOMFACTOR! We just convert the original
			// coordinates like they were the drawn ones, so that they become SCREEN
			// coordinates (regarding ZoomFactor) - then the rest of the check is trivial.
			// 2022-07-31_12-15: the following within-bounds-check with much precomputation
			// was verified by narrowing the draw area, it worked.
			// ->
			if ((DrawObject.Pos.x + DrawObject.Size.x) >= ZoomManager.GetNegZoomShiftXDivZoomFactor() && (DrawObject.Pos.y + DrawObject.Size.y) >= ZoomManager.GetNegZoomShiftYDivZoomFactor() && // x >= -y is the same as x + y >= 0 (maybe faster)!
				(DrawObject.Pos.x) < ZoomManager.GetDrawWidthMinusZoomShiftXDivZoomFactor() && (DrawObject.Pos.y) < ZoomManager.GetDrawHeightMinusZoomShiftYDivZoomFactor())
			{
				return true;
			}
		}

		return false;
	}

	protected boolean IsZombie()
	{
		return IsZombie;
	}

	protected double LimitExcitement(double ExcitementCurrent)
	{
		if (ExcitementCurrent < 0.0)
			ExcitementCurrent = 0.0;
		if (ExcitementCurrent > +1.0)
			ExcitementCurrent = +1.0;

		return ExcitementCurrent;
	}

	protected double LimitExcitementAllowNegative(double ExcitementCurrent)
	{
		// allow negative excitement because neuron input could be inhibiting

		if (ExcitementCurrent < -1.0)
			ExcitementCurrent = -1.0;
		if (ExcitementCurrent > +1.0)
			ExcitementCurrent = +1.0;

		return ExcitementCurrent;
	}

	protected void LoseExcitementAndAddExcitementForNextTick()
	{
		// NOTE: without ExcitementAddForNextTick, Lines in a succession got all
		// activated at once, depending on their order in the DrawObjects array!

		// validity (i.e. could appear in the real brain): the excitement shortly gets a spike...
		ExcitementCurrent += ExcitementAddForNextTick;

		ShiftExcitementHistory(ExcitementCurrent);

		// validity: ...and then "collapses" again to basic level
		ExcitementCurrent -= ExcitementLossPerTick;

		// verify and reset
		ExcitementCurrent = LimitExcitement(ExcitementCurrent);
		ExcitementAddForNextTick = 0.0; // reset for next usage
	}

	protected void MarkAsZombie()
	{
		IsZombie = true;
	}

	protected void Move(Point MoveAmount)
	{
		Pos.x += MoveAmount.x;
		Pos.y += MoveAmount.y;
	}

	protected void NegativeDrawRect(Graphics g, int x, int y, int width, int height)
	{
		// https://stackoverflow.com/questions/4213040/negative-number-in-fillrect/

		if (width < 0)
			x -= Math.abs(width);
		if (height < 0)
			y -= Math.abs(height);

		DrawRect(g, x, y, Math.abs(width), Math.abs(height));
	}

	public void ResetState()
	{ // meant to be called for EVERY DrawObject before beginning a new simulation

		if (IsInLearningNeuronState) // ELSE Strength was set BY USER, DO NOT RESET it!
		{
			Strength = 0;
		}
		for (int m = 0; m < HormoneStrengths.size(); m++)
			HormoneStrengths.set(m, 0.0);

		ExcitementCurrent = 0.0;
		ExcitementAddForNextTick = 0.0;

		ExcitementPartnerServedInThisTick = false;

		for (int m = 0; m < ExcitementHistoryLength; m++)
			ExcitementHistory[m] = 0.0;
		ExcitementHistoryWriteIndex = 0;
	}

	protected void SetColor(Graphics g, Color ColorNew)
	{
		g.setColor(ColorNew);

		/*// not faster, practically the same CPU time required for a tick of blinking_huge.ncide
		if (ColorNew != ColorLast)
		{
			g.setColor(ColorNew);
			ColorLast = ColorNew;
		}
		*/
	}

	protected void SetCompoundId(long CompoundIdNew)
	{
		if (CompoundIdNew >= 0 || CompoundIdNew == ID_UNSET)
			CompoundId = CompoundIdNew;
		else
			System.out.println("error in DrawObject.SetCompoundId(): passed value not useful!");
	}

	protected void SetHighlighted(boolean IsHighlightedNew)
	{
		IsHighlighted = IsHighlightedNew;
	}

	public void SetIsCommentField(boolean IsCommentFieldNew)
	{
		IsCommentField = IsCommentFieldNew;
	}

	public void SetIsLearningNeuron(boolean IsInLearningNeuronStateNew)
	{
		IsInLearningNeuronState = IsInLearningNeuronStateNew;
	}

	public void SetParentDrawObject(DrawObject ParentDrawObjectNew)
	{
		ParentDrawObject = ParentDrawObjectNew;
	}

	protected void SetPartOfMultiSelection(boolean IsPartNew)
	{
		IsPartOfMultiSelection = IsPartNew;
	}

	protected void SetPos(Point PosNew)
	{
		Pos = AlignOnGrid(PosNew);
	}

	protected void SetPosNoAlignOnGrid(Point PosNew)
	{
		Pos = PosNew;
	}

	protected void SetSize(Point SizeNew)
	{
		Size = SizeNew;
	}

	protected void SetStrength(int StrengthNew)
	{
		if (StrengthNew >= 0 && StrengthNew <= StrengthMax)
			Strength = StrengthNew;
		else
			System.out.println("error in DrawObject.SetStrength(): passed value invalid!");
	}

	protected void SetStrengthTrigger(int StrengthTriggerNew)
	{
		if (StrengthTriggerNew >= 0 && StrengthTriggerNew <= StrengthMax)
			StrengthTrigger = StrengthTriggerNew;
		else
			System.out.println("error in DrawObject.SetStrengthTrigger(): passed value invalid!");
	}

	protected void ShiftExcitementHistory(double ExcitementToAdd)
	{
		ExcitementHistory[ExcitementHistoryWriteIndex] = ExcitementToAdd;
		ExcitementHistoryWriteIndex++;
		if (ExcitementHistoryWriteIndex >= ExcitementHistoryLength)
			ExcitementHistoryWriteIndex = 0;
	}

	protected void StateFromFile(DataInputStream is) throws IOException
	{
		@SuppressWarnings("unused")
		short FileFormatVersionRead;

		FileFormatVersionRead = is.readShort(); // it might be necessary to skip writing or reading certain values to be "in sync", depending on version of the saved .ncide file

		if (FileFormatVersionRead >= 3)
			Properties = is.readLong();
		Pos.x = is.readInt();
		Pos.y = is.readInt();
		Size.x = is.readInt();
		Size.y = is.readInt();
		IsHighlighted = is.readBoolean() & false; // do not read this, it's not useful to be reloaded (as it is rather a temporary state)
		IsZombie = is.readBoolean();
		IsPartOfMultiSelection = is.readBoolean() & false; // do not read this, it's not useful to be reloaded (as it is rather a temporary state)
		CompoundId = is.readLong();
		Text = Tools.ReadUTF32Bit(is);
		StrengthMax = is.readInt();
		Strength = is.readInt();
		StrengthTrigger = is.readInt();
		IsNegative = is.readBoolean();
		IsInLearningNeuronState = is.readBoolean();
		IsCommentField = is.readBoolean();
		IsEnabled = is.readBoolean();

		int HormoneCount = is.readInt();

		if (HormoneNames == EmptyHormoneNames)
			HormoneNames = new ArrayList<String>();
		else
			HormoneNames.clear();

		if (HormoneStrengths == EmptyHormoneStrengths)
			HormoneStrengths = new ArrayList<Double>();
		else
			HormoneStrengths.clear();

		if (HormoneCount > 0)
		{
			HormoneNames.ensureCapacity(HormoneCount); // not sure if this is effective
			HormoneStrengths.ensureCapacity(HormoneCount); // not sure if this is effective
			for (int m = 0; m < HormoneCount; m++)
			{
				HormoneNames.add(is.readUTF());
				HormoneStrengths.add(is.readDouble());
			}
		}
	}

	protected void StateToFile(DataOutputStream os) throws IOException
	{
		os.writeShort(FileFormatVersion);
		os.writeLong(Properties);
		os.writeInt(Pos.x);
		os.writeInt(Pos.y);
		os.writeInt(Size.x);
		os.writeInt(Size.y);
		os.writeBoolean(IsHighlighted & false);
		os.writeBoolean(IsZombie);
		os.writeBoolean(IsPartOfMultiSelection & false);
		os.writeLong(CompoundId);
		Tools.WriteUTF32Bit(os, Text);
		os.writeInt(StrengthMax);
		os.writeInt(Strength);
		os.writeInt(StrengthTrigger);
		os.writeBoolean(IsNegative);
		os.writeBoolean(IsInLearningNeuronState);
		os.writeBoolean(IsCommentField);
		os.writeBoolean(IsEnabled);

		os.writeInt(HormoneNames.size()); // should always be the same as HormoneStrengths.size()!
		for (int m = 0; m < HormoneNames.size(); m++)
		{
			os.writeUTF(HormoneNames.get(m));
			os.writeDouble(HormoneStrengths.get(m));
		}
	}

	public void ToggleIsInLearningNeuronState()
	{
		IsInLearningNeuronState = !IsInLearningNeuronState;
	}
}
